package de.akabahn.ebd.dbd.impl;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;

import de.akabahn.ebd.comm.dbd.DBDInputListener;
import de.akabahn.ebd.comm.dbd.DBDParameter;
import de.akabahn.ebd.dbd.DBDAdapter;
import de.akabahn.ebd.dbd.DBDAdapterFactory;
import de.akabahn.ebd.dbd.DBDException;
import de.akabahn.ebd.dbd.DBDSignal;
import de.akabahn.ebd.dbd.DBDSubscriptionListener;
import de.akabahn.ebd.dbd.SwitchPosition;

/**
 * Class that simulates the DBD client application without connecting to the
 * server.<br>
 * Instances of this class should be created by using the
 * <code>DBDAdapterFactory</code> class.<br>
 * <br>
 * Currently supported commands are:<br>
 * <ul>
 * <li><code>Reload DBD variables</code></li>
 * <li><code>List available values</code>
 * <li><code>List available switches</code></li>
 * <li><code>Query variables</code></li>
 * <li><code>Set variables</code></li>
 * <li><code>Subscribe to variables</code></li>
 * <li><code>Unsubscribe from variables</code></li>
 * <li><code>Force variables</code></li>
 * <li><code>Unforce variables</code></li>
 * </ul>
 * 
 * @author Peter Eimann
 * @since 1.0
 * @version 1.0
 * @see DBDAdapterFactory
 */
public class DBDAdapterSimulation implements DBDAdapter, DBDInputListener, Runnable {

	private boolean started;
	private Map<String, List<DBDSubscriptionListener>> subscriptions;
	private Thread subscriberThread;
	private Random random;

	public DBDAdapterSimulation() {
		subscriptions = new HashMap<String, List<DBDSubscriptionListener>>();
		random = new Random(System.currentTimeMillis());
	}

	@Override
	public void onInputAvailable(DBDParameter dbd) {
	}

	@Override
	public void start() throws DBDException {
		if (!started) {
			started = true;
			subscriberThread = new Thread(this);
			subscriberThread.start();
		}
	}

	@Override
	public void stop() throws DBDException {
		if (started) {
			started = false;
			subscriberThread.interrupt();
			try {
				subscriberThread.join(5000);
			} catch (InterruptedException e) {
			}
		}
	}

	@Override
	public boolean isStarted() {
		return started;
	}

	@Override
	public void reloadDBDVariables() {
	}

	@Override
	public List<String> listAvailableVariables(String[] stationNumbers) throws DBDException {
		return null;
	}

	@Override
	public List<String> listAvailableSwitches(String[] stationNumbers) throws DBDException {
		return null;
	}

	@Override
	public DBDParameter query(String name) throws DBDException {
		return new DBDParameter(null, name, Math.abs(random.nextInt() % 3));
	}

	@Override
	public <T> T query(String name, Class<T> type) throws DBDException {
		DBDParameter res = new DBDParameter(null, name, Math.abs(random.nextInt() % 3));

		if (type == Integer.class) {
			return type.cast(Integer.valueOf(res.getValue()));
		} else if (type == SwitchPosition.class) {
			return type.cast(SwitchPosition.fromValue(res.getValue()));
		} else if (type == DBDSignalD.class) {
			return type.cast(new DBDSignalD(res.getValue()));
		} else if (type == DBDSignalF.class) {
			return type.cast(new DBDSignalF(res.getValue()));
		} else if (type == DBDParameter.class) {
			return type.cast(res);
		} else {
			throw new IllegalArgumentException("Unsupported class type " + type.getName());
		}
	}

	@Override
	public void set(String name, int value) throws DBDException {
	}

	@Override
	public void set(String name, SwitchPosition pos) throws DBDException {
	}

	@Override
	public void set(String name, DBDSignal signal) throws DBDException {
	}

	@Override
	public void force(String name, int value) throws DBDException {
	}

	@Override
	public void unforce(String name) throws DBDException {
	}

	@Override
	public void subscribe(String name, DBDSubscriptionListener listener) throws DBDException {
        List<DBDSubscriptionListener> subscribers = subscriptions.get(name.toUpperCase());
        if (subscribers == null) {
            subscribers = new CopyOnWriteArrayList<DBDSubscriptionListener>();
            subscriptions.put(name.toUpperCase(), subscribers);
        }
        subscribers.add(listener);
	}

	@Override
	public void unsubscribe(String name) {
		subscriptions.remove(name);
	}

    @Override
	public void unsubscribe(String name, DBDSubscriptionListener listener) {
        List<DBDSubscriptionListener> subscribers = subscriptions.get(name.toUpperCase());
        if (subscribers != null) {
        	if (subscribers.size() == 1) {
        		subscriptions.remove(name.toUpperCase());
        	} else {
        		subscribers.remove(listener);
            }
        }
	}

	@Override
	public void run() {
		do {
			for (String key : subscriptions.keySet()) {
				for (DBDSubscriptionListener subscriber: subscriptions.get(key)) {
					subscriber.onChange(new DBDParameter(null, key, Math.abs(random.nextInt() % 3)));
					try {
						Thread.sleep(250);
					} catch (InterruptedException e) {
					}
				}
			}
			try {
				Thread.sleep(5000);
			} catch (InterruptedException e) {
			}
		} while (started);
	}
}
